Skip to content

Conversation

@yoonsu417
Copy link
Contributor

#️⃣연관된 이슈

#201

📝작업 내용

5번 이상 틀렸을 경우 계정 잠금 및 12시간 후 자동 해제
8자 이상, 영문자/숫자/특수문자 최소 1번

🔎코드 설명

x

💬고민사항 및 리뷰 요구사항 (Optional)

x

비고 (Optional)

x

@yoonsu417 yoonsu417 requested a review from a team September 6, 2025 07:02
@yoonsu417 yoonsu417 self-assigned this Sep 6, 2025
@yoonsu417 yoonsu417 added the ✨ Feature 기능 개발 label Sep 6, 2025
Copy link
Member

@riadan710 riadan710 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전반적으로 작동엔 문제가 없어 보이지만,
CustomUserDetailService에서
isLockedNow를 사용하는 잠금여부확인 및 처리 부분과,
passwordEncoder.matches를 사용하는 비밀번호 확인 부분은 별도 private 메소드로 빼는게 어떨까 싶습니다.

Copy link
Member

@Jinho622 Jinho622 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잘 동작합니다!

@riadan710
Copy link
Member

/gemini review

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이번 PR은 로그인 실패 시 계정 잠금 기능과 새로운 비밀번호 정책을 도입하는 변경사항을 담고 있습니다. 전반적으로 올바르게 구현되었지만, 로그인 실패 처리 로직에 동시성 문제가 발생할 수 있는 부분이 있어 수정을 제안했습니다. 해당 부분은 여러 요청이 동시에 들어올 경우 시도 횟수가 정확하게 계산되지 않을 수 있습니다. 또한, 계정 잠금 시 사용자에게 표시되는 남은 시간 메시지를 더 명확하게 개선하는 방안도 제안했습니다. 비밀번호 유효성 검사 규칙과 로그인 처리에 대한 트랜잭션 설정은 올바르게 적용되었습니다.

Comment on lines 54 to +69
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("Password가 일치하지 않습니다.");
Users refreshed = userRepository.findByUsername(username).orElse(user);
int attempts = refreshed.getFailedAttempts();
int currentAttempts = attempts + 1;

if (currentAttempts >= MAX_ATTEMPTS) {
refreshed.lockFor(LOCK_DURATION);
userRepository.save(refreshed);
throw new LockedException("비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다.");
}

user.updateFailedAttempts(currentAttempts);
userRepository.save(user);

throw new BadCredentialsException("아이디 또는 비밀번호가 올바르지 않습니다. (남은 시도: " + (MAX_ATTEMPTS - currentAttempts) + "회)");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

로그인 실패 시 사용자 정보를 업데이트하는 로직에 동시성 문제가 발생할 수 있으며, 코드가 복잡합니다. userrefreshed라는 두 개의 다른 객체 인스턴스를 사용하여 데이터를 업데이트하고 있어, 동시 요청 시 데이터 불일치가 발생할 수 있습니다. 예를 들어, refreshed 객체로 잠금 여부를 확인하고 잠금을 설정하지만, 잠기지 않은 경우에는 원본 user 객체의 실패 횟수를 업데이트하고 저장합니다. 이로 인해 failedAttempts가 정확히 증가하지 않을 수 있습니다. 하나의 사용자 객체 인스턴스를 일관되게 사용하고, 데이터베이스 저장 연산을 한 번으로 줄여 코드를 더 명확하고 안전하게 만드는 것이 좋습니다.

        if (!passwordEncoder.matches(password, user.getPassword())) {
            // 동시 로그인 시도에 대한 경쟁 상태를 방지하기 위해 사용자 정보를 다시 가져옵니다.
            Users userToUpdate = userRepository.findByUsername(username)
                    .orElseThrow(() -> new UsernameNotFoundException("해당 아이디를 가진 유저가 존재하지 않습니다: " + username));

            int newAttempts = userToUpdate.getFailedAttempts() + 1;
            userToUpdate.updateFailedAttempts(newAttempts);

            if (newAttempts >= MAX_ATTEMPTS) {
                userToUpdate.lockFor(LOCK_DURATION);
            }
            userRepository.save(userToUpdate);

            if (newAttempts >= MAX_ATTEMPTS) {
                throw new LockedException("비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다.");
            } else {
                throw new BadCredentialsException("아이디 또는 비밀번호가 올바르지 않습니다. (남은 시도: " + (MAX_ATTEMPTS - newAttempts) + "회)");
            }
        }

Comment on lines +37 to +46
long remainHours = remain.toHours();
long remainMinutes = remain.toMinutes();

String message;
if (remainHours >= 1) {
message = "비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다. 약 " + (remainHours + 1) + "시간 후에 다시 시도해주세요.";
} else {
long remainMin = Math.max(1, remainMinutes);
message = "비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다. 약 " + remainMin + "분 후에 다시 시도해주세요.";
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

계정 잠금 시 남은 시간을 안내하는 메시지가 사용자에게 혼란을 줄 수 있습니다. 현재 로직은 남은 시간을 시간 단위로 올림하여 보여주기 때문에, 예를 들어 1시간 1분이 남았을 때와 1시간 59분이 남았을 때 모두 "약 2시간"으로 표시됩니다. 사용자에게 더 정확한 정보를 제공하기 위해 시간과 분을 함께 표시하는 것을 고려해보세요.

Suggested change
long remainHours = remain.toHours();
long remainMinutes = remain.toMinutes();
String message;
if (remainHours >= 1) {
message = "비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다. 약 " + (remainHours + 1) + "시간 후에 다시 시도해주세요.";
} else {
long remainMin = Math.max(1, remainMinutes);
message = "비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다. 약 " + remainMin + "분 후에 다시 시도해주세요.";
}
long remainHours = remain.toHours();
long remainMinutesPart = remain.toMinutes() % 60;
String message;
if (remainHours >= 1) {
message = "비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다. 약 " + remainHours + "시간 " + remainMinutesPart + "분 후에 다시 시도해주세요.";
} else {
long remainMin = Math.max(1, remain.toMinutes());
message = "비밀번호 " + MAX_ATTEMPTS + "회 오류로 계정 잠긴 상태입니다. 약 " + remainMin + "분 후에 다시 시도해주세요.";
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants